﻿namespace Bildverarbeitung.CGFilter
{
    using System;
    using System.Drawing;
    using Bildverarbeitung;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using OpenSURFcs;

    /// <summary>
    /// 
    /// </summary>
    public class KlomannMarcelFilter : Bildverarbeitung.IFilter
    {
        private const int TOLERANCE_RADIUS = 15;
        private const int TOLERANCE_RADIUS_COMBINE = 0;

        /// <summary>
        /// Gets the summerized difference from average of all images.
        /// </summary>
        /// <param name="images">Given images.</param>
        /// <returns>The summerized difference.</returns>
        public Bitmap GetImageWithAllSurfFeatures(Bitmap[] images)
        {
            if (images == null || images.Length == 0)
            {
                throw new ArgumentNullException("Liste ist leer.");
            }
            int[] rotations = new int[] { -1, 10, 20, 30, 45, 90, 135, 180, 0, 0, 0, 0, 0, 0 };

            float halfUnrotatedWidth = images[0].Width / 2f;
            float halfUnrotatedHeight = images[0].Height / 2f;

            List<IPoint>[] ipts = new List<IPoint>[images.Length];
            List<IPoint> resultPoints = new List<IPoint>();

            Parallel.For(0, images.Length, i =>
            {
                IntegralImage iimage = IntegralImage.FromImage(images[i]);
                List<IPoint> list = FastHessian.getIpoints(0.0002f, 5, 2, iimage);
                SurfDescriptor.DecribeInterestPoints(list, false, false, iimage);
                ipts[i] = list;

                if (TOLERANCE_RADIUS_COMBINE > 1) // Zusammenfassen von Punkten ist ab einem Wert von 2 aktiviert.
                {
                    // Zusammenfassen nahe zusammenliegender Punkte
                    for (int j = 0; j < list.Count; j++)
                    {
                        // Einen Referenzpunkt in der Liste raussuchen
                        IPoint referencePoint = list[j];
                        float refX = referencePoint.x;
                        float refY = referencePoint.y;
                        float sumX = refX;
                        float sumY = refY;
                        float count = 1;
                        for (int k = j + 1; k < list.Count; k++)
                        {
                            // Einen in der Liste dahinter befindlichen Punkt als zweiten Referenzpunkt heranziehen
                            IPoint referencePoint2 = list[k];
                            float refX2 = referencePoint2.x;
                            float refY2 = referencePoint2.y;
                            float diffX = refX2 - refX;
                            float diffY = refY2 - refY;

                            if (diffX * diffX + diffY * diffY <= TOLERANCE_RADIUS_COMBINE * TOLERANCE_RADIUS_COMBINE) // Zum Vergleich wird das Quadrat des Abstandes zw. 2 Punkten genommen
                            {   // Punkt liegt innerhalb der Toleranz, d.h. er wird zusammengefasst
                                sumX += refX2;
                                sumY += refY2;
                                count++;

                                // Punkt entfernen, da er nicht mehr berücksichtigt wird
                                list.RemoveAt(k);
                                k--;
                            }
                        }

                        // Hier wird ein Durchschnittspunkt aus allen in der Nähe (entsprechend der Toleranz) befindlichen Punkten gebildet.
                        referencePoint.x = sumX / count;
                        referencePoint.y = sumY / count;
                    }
                }

                if (rotations[i] > 0)
                {
                    Bitmap currentImage = images[i];
                    double rad = rotations[i] * (Math.PI / 180.0);
                    double halfWidth = currentImage.Width / 2.0;
                    double halfHeight = currentImage.Height / 2.0;

                    // Punkte rotieren
                    for (int j = 0; j < list.Count; j++)
                    {
                        IPoint point = list[j];
                        float tmpX;
                        float tmpY;

                        // Mittelpunkt des Bildes in den Ursprung verschieben
                        point.x -= (float)halfWidth;
                        point.y -= (float)halfHeight;

                        tmpX = point.x;
                        tmpY = point.y;

                        // Punkte um den Ursprung rotieren
                        point.x = (float)(tmpX * Math.Cos(rad) - tmpY * (Math.Sin(rad)));
                        point.y = (float)(tmpX * Math.Sin(rad) + tmpY * (Math.Cos(rad)));

                        // Punkte zurückverschieben (unter Verwendung der Werte eines unrotierten Bildes)
                        point.x += halfUnrotatedWidth;
                        point.y += halfUnrotatedHeight;
                    }
                }
                else if (rotations[i] < 0)
                {
                    int halfWidth = images[i].Width / 2;

                    for (int j = 0; j < list.Count; j++)
                    {
                        IPoint point = list[j];
                        point.x = halfWidth + (halfWidth - point.x);
                    }
                }
            });

            // Naheliegende Punkte wurden zusammengefasst, jetzt müssen die Bilder miteinander verglichen werden und wiederholt auftauchende Punkte erkannt werden.
            List<IPoint> firstList = ipts[0];
            Parallel.For(0, firstList.Count, i =>
            {
                // Referenzpunkt aus der Punktliste des ersten Bildes
                IPoint referencePoint = firstList[i];
                float refX = referencePoint.x;
                float refY = referencePoint.y;
                bool pointInAllImages = true;

                // Über alle anderen Listen iterieren und mit den dort vorhandenen Punkten vergleichen
                for (int j = 1; j < ipts.Length; j++)
                {
                    bool foundPoint = false;
                    List<IPoint> currentList = ipts[j];
                    lock (currentList)
                    {
                        // Referenzpunkt mit jedem einzelnen Punkt der aktuellen Liste vergleichen
                        for (int k = 0; k < currentList.Count; k++)
                        {
                            IPoint referencePoint2 = currentList[k];
                            float refX2 = referencePoint2.x;
                            float refY2 = referencePoint2.y;
                            float diffX = refX2 - refX;
                            float diffY = refY2 - refY;

                            // Wenn Abstand kleiner als Toleranz ist, wird der Punkt als vorhanden angesehen
                            if (diffX * diffX + diffY * diffY <= TOLERANCE_RADIUS * TOLERANCE_RADIUS)
                            {
                                //currentList.RemoveAt(k);
                                foundPoint = true;
                                break;
                            }
                        }
                    }
                    // Kein Punkt gefunden in einem der anderen Bilder, also suche erfolglos
                    if (!foundPoint)
                    {
                        pointInAllImages = false;
                        break;
                    }
                }
                // Wenn ein Featurepunkt in allen Bildern gefunden wurde, wird er zur Ergebnismenge hinzugefügt.
                if (pointInAllImages)
                {
                    lock (resultPoints)
                    {
                        resultPoints.Add(referencePoint);
                    }
                }
            });


            // Zeichnen der Featurepunkt in das Bild, kopiert aus dem Beispiel vom openSURF Quellcode.
            Graphics g = Graphics.FromImage(images[8]);
            Pen redPen = new Pen(Color.Red);
            Pen bluePen = new Pen(Color.Blue);
            Pen myPen;

            foreach (IPoint point in resultPoints)
            {
                int S = 2 * Convert.ToInt32(2.5f * point.scale);
                int R = Convert.ToInt32(S / 2f);

                Point pt = new Point(Convert.ToInt32(point.x), Convert.ToInt32(point.y));
                Point ptR = new Point(Convert.ToInt32(R * Math.Cos(point.orientation)),
                             Convert.ToInt32(R * Math.Sin(point.orientation)));

                myPen = (point.laplacian > 0 ? bluePen : redPen);

                g.DrawEllipse(myPen, pt.X - R, pt.Y - R, S, S);
                g.DrawLine(new Pen(Color.FromArgb(0, 255, 0)), new Point(pt.X, pt.Y), new Point(pt.X + ptR.X, pt.Y + ptR.Y));
            }

            return images[8];
        }

        /// <summary>
        /// Return the author´s name
        /// Please use NachnameVorname (without spaces).
        /// </summary>
        /// <returns>author´s name</returns>
        public String GetName()
        {
            return ("KlomannMarcel");
        }
    }
}
